\\\*** PELock – Manual lock picking ***///

***** ***** by crUsAdEr ***** ****
 

This tutorial aims to discuss about PElock 1.06 protection. Various techniques are employed to prevent crackers from fooling with this protector, so in this tutorial I hope to reverse some interesting features of PELock and eventually remove the protection layer. PELock is highly OS dependent and this tutorial is done purely on win2k so some protection features discussed here will be different on other OS.

TOOLS used :
IDA 4.15
Soft Ice on Win2k  SP3
LordPE Deluxe
Superbpm for NT/2k
Imprec 1.4.2

Targets : PELock 1.06
Included : PElock.idc

Prologue

PELock is a very interesting protection with many features similar to SVKProtector especially IAT redirection, code decryption after OEP. PELock is also able to detect icedump on win98 by exploiting the fact that icedump leaves kernel32.dll memory unprotected (as discussed by SplAj on Fravia Message Board). Though I try to make this essay as reader-friendly as possible, I assume that readers have some background in unpacking, have thorough knowledge of Structured Exception Handling and are able to use the tools listed above comfortably. PELock does extensive bpx checking by checking opcodes for "CC" so throughout this exercise, do not use bpx on API or PELock will throw a tantrum and exit silently. Also, PELock clears debug register with via SEH so you should have SuperBPM running at all time.

 (Finally, though it is not essential, I recommend readers to read my two previous essays on AsProtect 1.2 and Armadillo 2.6 to gain some background knowledge as I will not be repeating the same points covered in those essays.)

1. Playing with the lock

OK, I hope you have already downloaded PELock and discovered that it wont run together with softice. We will worry about it later! The heart of PELock or as many other protector, lies in its loader that performs the target file decryption, import loading and we will set out obtaining a valid dump of PELock loader first. Load Superbpm for NT/2k with softice and do "bpm VirtualAlloc x". Run PELock, once softice breaks, you will land here

00431682     push 4
00431684     push 3000h
00431689     push 0F000h		; loader size
0043168E     push ecx
0043168F     mov edx, [ebp+VirtualAlloc]
00431692     cmp byte ptr [edx], 0CCh   ; check opcodes for bpx
00431695     setz cl			; if bpx found then 
00431698     add edx, ecx               ; change value of edx and causes error
0043169A     call edx
0043169C     lea edx, [ebp+756h]   <===  YOU SHOULD LAND HERE
004316A2     push eax			; here eax is the address of VirtualAlloc memory
004316A3     push edx			; where the main PELock loader will be decrypted to
004316A4     push eax
004316A5     pusha
<-----------snip------------->
<--loader decryption routine-->
00431768     mov [esp+1Ch], edi
0043176C     popa
0043176D     retn		; jumps to PELock loader code from here

As you have seen above, this is where PELock loader is decrypted into memory. The long decryption routine is removed but you can simply scroll down in softice windows to find the "ret" instruction. We need to put a break point at this address 43176D and make a dump of this PELock loader, you can look in stack to find out the base address of our new decrypted loader. Once dumped, we can load this loader into IDA to disassemble and study. I trust you know how to do this by building a simple PE Header for our dump or cut and paste into PELock.exe and uses Reload Input File in IDA.

Once you manage to load PELock loader into IDA, you will see this:

00401000     call near ptr PE_Lock_start+24h
00401005 
00401005 GetAPIAddress proc near       ; CODE XREF: ...
00401005 
00401005 var_4= byte ptr -4
00401005 arg_0= dword ptr  8
00401005 arg_4= dword ptr  0Ch
00401005 arg_8= dword ptr  10h
00401005 arg_C= byte ptr  14h
00401005 arg_104= dword ptr  10Ch
00401005 
00401005     push ebp
00401006     mov ebp, esp
00401008     pusha
<---------continue---------->

The snippet above are a few first instruction of PELock loader, starting with a "home-made" GetProcAddress routine that read system dll PE Header and traverse its export directory to find API address. API names are hashed and compared with tables (we will discuss more about this in API redirection section). Once each API address is found, the first byte is checked against "CC" and if bpx is detected then the execution flow will be changed slightly causing PELock to eventually exit in silence. As the whole loader is not fully decrypted yet, various parts of the loader are still encrypted and would only be decrypted later as PELock proceed to loading its main exe so as you debug and run PElock, if you find that IDA disassembly is different from softice screen then you will need to make a new dump of the loader and paste the newly decrypted loader code over the encrypt code for IDA to disassemble them properly.

As you start tracing PELock loader, you will realize that there are a lot garbage code inserted in the loader to make tracing and disassembling difficult. Here are a few typical examples of garbage code:

0040159F C1 F7 00              sal edi, 0
004015A2 C1 F2 00              sal edx, 0		; do nothing
004015A5 EB 02                 jmp short loc_4015A9	; jump over
004015A5                   ; -----------------------------------------------------------
004015A7 CD                    db 0CDh ; -
004015A8 20                    db  20h ;  
004015A9                   ; -----------------------------------------------------------
004015A9                   
004015A9                   loc_4015A9:                   ; CODE XREF: ...
004015A9 C1 F0 00              sal eax, 0
004015AC EB 02                 jmp short loc_4015B0
004015AC                   ; -----------------------------------------------------------
004015AE C9                    db 0C9h ; +
004015AF 22                    db  22h ; "
004015B0                   ; -----------------------------------------------------------
004015B0                   
004015B0                   loc_4015B0:                   ; CODE XREF: ...
004015B0 E8 01 00 00 00        call loc_4015B6		; call over head
004015B0                   ; -----------------------------------------------------------
004015B5 42                    db  42h ; B
004015B6                   ; -----------------------------------------------------------
004015B6                   
004015B6                   loc_4015B6:                   ; CODE XREF: ...
004015B6 8D 64 24 04           lea esp, [esp+4]		; correct stack
	OR THIS
004015F1 E8 01 00 00 00        call loc_4015F7
004015F1                   ; -----------------------------------------------------------
004015F6 3B                    db  3Bh ; ;
004015F7                   ; -----------------------------------------------------------
004015F7                   
004015F7                   loc_4015F7:                   ; CODE XREF: ...
004015F7 8F 44 24 FC           pop dword ptr [esp-4]	; correct stack
	OR THIS
004015D6 7A 03                 jp  short loc_4015DB	; doesn't matter the status of EFlags
004015D8 7B 01                 jnp short loc_4015DB	; one of these jumps will jump
004015D8                   ; -----------------------------------------------------------
004015DA 7E                    db  7Eh ; ~
004015DB                   ; -----------------------------------------------------------
004015DB                   
004015DB                   loc_4015DB:                   ; CODE XREF: ...
004015DB EB 02                 jmp short loc_4015DF	; garbage code
004015DB                   ; -----------------------------------------------------------
004015DD 8B                    db  8Bh ; ï
004015DE 8D                    db  8Dh ; ì
004015DF                   ; -----------------------------------------------------------
004015DF                   
004015DF                   loc_4015DF:                   ; CODE XREF: ...
004015DF C1 F6 00              sal esi, 0

It is very important to remove these garbage codes that do nothing from our disassembly so that we can have a better over view of the loader codes as well as save us time from correcting the disassembly in IDA. You can try using various tools like MOW, Imhotep etc or code your own tool to clean out the garbage. I coded myself a simple idc script which checks the opcodes pattern for these garbage code and NOP them out, displaying them as single line array. Further code snippet of this tutorial are taken after most garbage code had been removed. I have attached my idc script above for reference purpose, you can try coding your own script to suit your need.

2. Softice detection

Once you have cleaned out most of the garbage code of PELock, you can then comfortably proceed on to study PELock but first we need PELock to run in the presence of Softice. Under win2k, PELock has too effective softice detection which is int1 detection and UnhandledExceptionFilter byte check.

When softice is loaded, softice replaces the first byte of kernel32!UnhandledExceptionFilter API with a "CC" instead of the usual "55". PELock checks this byte for "CC" as follow

004010D4 80 38 CC              cmp byte ptr [eax], 0CCh ; '¦'
004010D7 75 0F                 jnz short OK
004010D9 74 01                 jz  short near ptr bad

Thus, to bypass this check, we place a memory READ breakpoint on UnhandledExceptionFilter and change the Zero flag.
bpm UnhandledExceptionFilter r do "r fl z"

When Windows 2000 is running without softice, INT01 descriptor entry in IDT has DPL=0. So if a ring-3 task attempts to call INT01 with DPL=0, a GP=INT0D fault will occur and Windows INT0D handler will report C0000005 (EXCEPTION_ACCESS_VIOLATION) as the exception code and Eip as address where exception occur. When ntice is loaded, it sets DPL=3 for INT01 descriptor entry in IDT and now INT01 can be accessed by normal ring-3 task. Now if an INT01 is encountered, the next instruction address is stored on stack instead and Windows will check for any breakpoints placed, if not found then Windows will report a 80000004 (EXCEPTION_SINGLE_STEP) exception. In short, if softice is loaded, then the exception code is 80000004 and exception address is EIP+2 when int1 is encountered while if softice is not loaded, the exception code would be C0000005 and exception address would be EIP. (For detail reference, please refer to Intel Manual Volume 3).         Thanks [Yates] for the explanation!

PELock uses the fact that exception addresses differ to check if softice is loaded. An normal SEH handler is set up!

004044A3 CD 01                 int 1    ; exception address here if softice is not loader
004044A5 C3                    retn	; exception address here if softice loaded
004044A6                   ; -----------------------------------------------------------
004044A6 EB 36                 jmp short near sice_not_present
004044A6                   ; -----------------------------------------------------------
004044A8 90 90 90 90 90 90+    Softice_detected db 35h dup(90h)
004044DD                   ; -----------------------------------------------------------
004044DD C3                    retn
004044DD                   ; -----------------------------------------------------------
004044DE 90 90 90 90 90 90+sice_not_present db 40h dup(90h) 

<--- Inside SEH handler --->
004040E4 83 A8 B8 00 00 00+    sub [eax+CONTEXT.Eip], 0FFFFFFFDh ; add Eip by 3 bytes
004040E4 FD                                     ; if sice is present, int1 exception
004040E4                                        ; will move Eip ahead by 2 bytes already 

Hence, if softice is detected, the execution flow will change and soon PELock will exit silently. To overcome this detection, we need to patch IDT entry for INT01 descriptor so that DPL=0 again. In win2k do
idt            ; to obtain address of Interrupt Descriptors Table
d <address of IDT>
Change the 0Dh byte from IDT on the table from EE to 8E    Thanks SiNTaX and Nikolatesla for the tip
For more detailed explanation, you can refer to Art Of Assembly about IDT format.

3. Import Rebuilding

Once you are done with IDT patching, UnhandedExceptionFilter will break twice but PELock will happily run with softice loaded. Our next task is then to obtain valid import data. Run PELock, at the first UnhandledExceptionFilter breakpoint, you will be inside the "home-made" GetProcAddress described above, do "bpm Eip x" to place a breakpoint in this routine, when softice breaks at this breakpoint, press F12 once and you will be in the middle of IAT loading and redirecting process.

00405350 EB 2A                 jmp short near ptr ImportLoadingStart
00405350                   ; ------------------------------------------------------
00405352 90 90 90 90 90 90+next_dll_entry db 27h dup(90h) ; CODE XREF: seg000:004059C3j
00405379                   ; ------------------------------------------------------
00405379 8D 7E 01              lea edi, [esi+1]
00405379                   ; ------------------------------------------------------
0040537C 90 90 90 90 90 90+ImportLoadingStart db 24h dup(90h) ; CODE XREF: seg000:00405350j
004053A0                   ; ------------------------------------------------------
004053A0 57                    push edi        ; edi -> DLL name
004053A1 FF 55 00              call [ebp+API.LoadLibrary]
004053A1                   ; ------------------------------------------------------
004053A4 90 90 90 90 90 90+    db 2Ch dup(90h)
004053D0                   ; ------------------------------------------------------
004053D0 85 C0                 test eax, eax
004053D2 0F 84 13 1A 00 00     jz  Library_loading_fail
004053D2                   ; ------------------------------------------------------
004053D8 90 90 90 90 90 90+    db 26h dup(90h)
004053FE                   ; ------------------------------------------------------
004053FE 8B D0                 mov edx, eax    ; edx = DLL handle
004053FE                   ; ------------------------------------------------------
00405400 90 90 90 90 90 90+    db 2Dh dup(90h)
0040542D                   ; ------------------------------------------------------
0040542D 8B F7                 mov esi, edi    ; import data block pointer
0040542D                   ; ------------------------------------------------------
0040542F 90 90 90 90 90 90+    db 2Dh dup(90h)
0040545C                   ; ------------------------------------------------------
0040545C                   string_scan_loop:   ; CODE XREF: seg000:00405461j
0040545C 8A 06                 mov al, [esi]   ; simple loop scan along Import Data block
0040545E 46                    inc esi         ; to mov pointer to the end of DLL name
0040545F 84 C0                 test al, al     ; by checking for null byte
00405461 75 F9                 jnz short string_scan_loop 
00405461                   ; ------------------------------------------------------
00405463 90 90 90 90 90 90+not_end_of_thunk db 4Eh dup(90h) ; CODE XREF: seg000:00405925j
004054B1                   ; ------------------------------------------------------
004054B1 8B 46 01              mov eax, [esi+1] ; get API name hash from Import Data Block
004054B1                   ; ------------------------------------------------------
004054B4 90 90 90 90 90 90+    db 2Ah dup(90h)
004054DE                   ; ------------------------------------------------------
004054DE B9 B2 06 00 00        mov ecx, 6B2h   ; initialize counter for CRC loop below
004054DE                   ; ------------------------------------------------------
004054E3 90 90 90 90 90 90+    db 23h dup(90h)
00405506                   ; ------------------------------------------------------
00405506                   CRC_import_decrypt_loop: ; CODE XREF: seg000:00405510j
00405506 2B 84 8D 0A 3D 00+    sub eax, [ebp+ecx*4+3D0Ah] ; loop decrypting API name hash
0040550D D3 C0                 rol eax, cl     ; using CRC of the code area around here
0040550F 49                    dec ecx         ; to prevent patching
00405510 75 F4                 jnz short CRC_import_decrypt_loop 
00405510                   ; ------------------------------------------------------
00405512 90 90 90 90 90 90+    db 28h dup(90h)
0040553A                   ; ------------------------------------------------------
0040553A 8A 0E                 mov cl, [esi]   ; get the first char of API name
0040553C 83 C6 05              add esi, 5
0040553C                   ; ------------------------------------------------------
0040553F 90 90 90 90 90 90+    db 23h dup(90h)
00405562                   ; ------------------------------------------------------
00405562 51                    push ecx        ; push first_char
00405562                   ; ------------------------------------------------------
00405563 90 90 90 90 90 90+    db 23h dup(90h)
00405586                   ; ------------------------------------------------------
00405586 50                    push eax        ; push API_name_hash
00405586                   ; ------------------------------------------------------
00405587 90 90 90 90 90 90+    db 32h dup(90h)
004055B9                   ; ------------------------------------------------------
004055B9 52                    push edx	       ; push DLL handle
004055B9                   ; ------------------------------------------------------
004055BA 90 90 90 90 90 90+    db 29h dup(90h)
004055E3                   ; ------------------------------------------------------
004055E3 FF 75 00              push dword ptr [ebp+0]
004055E3                   ; ------------------------------------------------------
004055E6 90 90 90 90 90 90+    db 33h dup(90h)
00405619                   ; ------------------------------------------------------
00405619 E8 E7 B9 FF FF        call GetAPIAddress ; The home-made GetProcAddress
00405619                   ; ------------------------------------------------------
0040561E 90 90 90 90 90 90+    db 30h dup(90h)   <=== YOU SHOULD LAND HERE
				but these garbage code are removed by my idc script
0040564E                   ; ------------------------------------------------------
0040564E 85 C0                 test eax, eax   ; check API address found
0040564E                   ; ------------------------------------------------------
00405650 90 90 90 90 90 90+    db 1Dh dup(90h)
0040566D                   ; ------------------------------------------------------
0040566D 0F 84 92 17 00 00     jz  API_add_not_found
0040566D                   ; ------------------------------------------------------
00405673 90 90 90 90 90 90+    db 5Bh dup(90h)
004056CE                   ; ------------------------------------------------------
004056CE F6 C1 80              test cl, 80h    ; check redirection flag
004056CE                                       ; if cl=80 then API is to be redirected
004056CE                   ; ------------------------------------------------------
004056D1 90 90 90 90 90 90+    db 2Bh dup(90h)
004056FC                   ; ------------------------------------------------------
004056FC 8B 0E                 mov ecx, [esi]
004056FC                   ; ------------------------------------------------------
004056FE 90 90 90 90 90 90+    db 47h dup(90h)
00405745                   ; ------------------------------------------------------
00405745 0F 84 87 00 00 00     jz  near ptr redirect_import+2Bh ; our CRUCIAL JUMP
00405745                   ; ------------------------------------------------------
0040574B 90 90 90 90 90 90+    db 55h dup(90h)
004057A0                   ; ------------------------------------------------------
004057A0 89 01                 mov [ecx], eax  ; update First Thunk of Import Directory
004057A2 E9 1F 01 00 00        jmp next_entry_in_thunk ; with original API address
004057A2                   ; ------------------------------------------------------
004057A7 90 90 90 90 90 90+redirect_import db 8Bh dup(90h) ; CODE XREF: seg000:00405745j
00405832                   ; ------------------------------------------------------
00405832 89 19                 mov [ecx], ebx  ; update First Thunk with redirected API address
00405832                   ; ------------------------------------------------------
00405834 90 90 90 90 90 90+    db 5Dh dup(90h)
00405891                   ; ------------------------------------------------------
00405891 E8 51 01 00 00        call near ptr redirect_API+1Fh
00405891                   ; ------------------------------------------------------
00405896 90 90 90 90 90 90+    db 30h dup(90h)
004058C6                   ; ------------------------------------------------------
004058C6                   next_entry_in_thunk: ; CODE XREF: seg000:004057A2j
004058C6 83 C6 04              add esi, 4
004058C6                   ; ------------------------------------------------------
004058C9 90 90 90 90 90 90+    db 30h dup(90h)
004058F9                   ; ------------------------------------------------------
004058F9 B1 FE                 mov cl, 0FEh ; '¦' ; check for stopping byte
004058F9                   ; ------------------------------------------------------
004058FB 90 90 90 90 90 90+    db 28h dup(90h)
00405923                   ; ------------------------------------------------------
00405923 38 0E                 cmp [esi], cl   ; if found then it is the end of thunk
00405925 0F 85 60 FB FF FF     jnz near ptr not_end_of_thunk+28h
00405925                   ; ------------------------------------------------------
0040592B 90 90 90 90 90 90+    db 32h dup(90h)
0040595D                   ; ------------------------------------------------------
0040595D 38 4E 01              cmp [esi+1], cl ; check for stopping byte again
0040595D                   ; ------------------------------------------------------
00405960 90 90 90 90 90 90+    db 28h dup(90h)
00405988                   ; ------------------------------------------------------
00405988 0F 84 FC 17 00 00     jz  import_done ; if  2 stopping byte together 
00405988                                       ; it is the end of Import Data
00405988                   ; ------------------------------------------------------
0040598E 90 90 90 90 90 90+    db 35h dup(90h)
004059C3                   ; ------------------------------------------------------
004059C3 E9 8A F9 FF FF        jmp near ptr next_dll_entry

I hope the comments are sufficient. Basically, Import Data is stored in a big block with "FE" used as a stopping byte or marker. Each DLL import entry is started with DLL name in plain ASCII, followed by import entries table and ended with "FE". Each import entry in the table comprises of 5 bytes, the first byte is first ASCII char of API name, the next dword is the API name hash. Here is an example of  Import Data block :

00407E21 6B 65 72 6E 65 6C+aKernel32_dll_0 db 'kernel32.dll',0
00407E2E 56                    db  56h ; V
00407E2F B5 F2 31 F6           dd 0F631F2B5h
00407E33 56                    db  56h ; V
00407E34 22 0A AC 32           dd 32AC0A22h
<-------------SNIP------------->
00407E60 47                    db  47h ; G
00407E61 E4 0D 06 DE           dd 0DE060DE4h
00407E65 FF                    db 0FEh ;  	stopping byte
00407E66 75 73 65 72 33 32+aUser32_dll db 'user32.dll',0
00407E71 4D                    db  4Dh ; M
00407E72 71 A2 D2 A8           dd 0A8D2A271h
00407E76 FF                    db 0FEh ;	stopping byte  
00407E77 FF                    db 0FEh ;  

Each import entry above is then passed as parameters for the "home-made" GetProcAddress which (assumes that system DLL export directories are alphabetically sorted) will then parse the export directory until it finds a match on the first char. It will then start performing hash on each API name of the Export Directory until a match for the required hash is found, this API address found will then be check for bpx then return to eax by the routine. As you can see, the compare at 4056CE and the conditional jump at 405745 are key decider on whether an API should be redirected or not. We would like PELock not to redirect any API at all but patching the jump wont do simply because the code CRC is used to calculate our import hash values. There are two solution to this, patching the "home-made" GetProcAddress to change value of ecx to 0 so that the jz at 405745 will always not jump or switching the zero flag at 405745 if zero flag is set. I opted for the second method by using this simple command below in softice.
bpm 405745 x if ZFL==1 do "r fl z; x"

Softice screen will flicker a lot as the breakpoint is hit pretty often (wonder why :) and there are quite a lot of imports so if you stand up, do a bit of stretching away from the monitor for a minute or so, when you come back PELock will already be running as if nothing had changed. BUT run Imprec now, enter OEP as 1000 and it will automatically find IAT for you at 28000. Click Get Import and Import will be able to resolve all import normally except 1. It is left as an exercise for readers to find out which API it is and why it is still redirected.

4. Decrypting the target EXE

Once you have fixed the IAT redirection routine and obtain a valid Import tree for Imprec, save it and put it aside. It is time to fully decrypt PElock.exe and obtain a full working dump. Run PELock again and launch LordPE, select Partial Dump and enter dump size as PELock.exe image size. Disassemble the dump in IDA you will realize that there are lots of code which are still encrypted and decrypted only when needed. Here is a snippet example of code still encrypted after OEP is reached :

00401ECA E8 33 5A EF FF        call near ptr 2F7902h   ; CALL Decryptor
00401ECA                   ; ----------------------------------------------------------
00401ECF E8 02 00 00           dd 2E8h			; code block property
00401ED3 66 7B 6B 3C 47 89+    db 66h, 7Bh, 6Bh, 3Ch, 47h, 89h, 0EDh, 39h, 53h, 0EFh, 83h,
00401ED3 ED 39 53 EF 83 00+    db 72h, 6Fh, 5Dh, 3Ch, 0EEh, 26h, 0B0h, 78h, 0AEh, 29h, 98h
00401ED3 B7 A1 AD 9D E8 B2+    db 0FBh, 0EBh, 0FAh, 0EBh, 0FCh, 0EBh, 6, 0CDh, 20h, 0EBh, 
00401F40                   ; -------------------------------------------------------------
00401F40 E8 F1 61 EF FF        call near ptr 2F8136h  ;  CALL Re-encryptor
00401F40                   ; -------------------------------------------------------------
00401F45 E8 02 00 00           dd 2E8h		      ; code block property
00401F49                   ; -------------------------------------------------------------

As you can see, there are a lot of block of codes as above in our dump.exe and the Decryptor and Re-encryptor address are not within the image of the exe but in PE loader memory instead. Basically, to prevent unpacking and dumping, PELock inserted a lot of these blocks of encrypted code into the original exe, when these codes need to be executed, the Decryptor will be called first, which read the code block property then decrypt the block accordingly, then return from the Decrypt call, execute the code block, call Re-encryptor to re-crypt the code block back again before moving onto normal execution again. Further investigation of dump.exe  shows that there are 3 different pairs of Decrypt-Encrypt call with different addresses and different algorithms employed.

Checking these routines in our loader, we will see that the dword below each CALL commented as "code block property" is an encrypted value of the code block length. Though a thorough understanding of Decryptors algorithm is not required to obtain good dump of the target exe, it is useful to go through them to understand roughly how they work. To keep this essay within reasonable length, I will not discussed the algorithm here.

In order to obtain a good dump, we need to force PELock loader to decrypt all these code blocks before reaching OEP. We can patch the Decryptor to decrypt the code blocks and overwrite call to encryptor with simple jump over but how do we know we have decrypted ALL the encrypted blocks left behind after OEP is reached? I was thinking about this problem and decided that PELock loader will need to know where to insert these Call to Decryptor and Re-encryptor as the loader offset will be different in different computer, hence PELock loader will need to calculate the call distance each time it loads up and insert these distances into the target exe so there must be a kind of table that store the offset of where these Calls should be inserted. Indeed that was the case. Re-run PELock and place bpm write one of these calls to Decryptor in the original EXE image and softice will break right in the middle of these routine.

0040DBA8                   insert_crypt_engine_3: ; CODE XREF: seg000:00407578p
0040DBA8 60                    pusha
0040DBA9 E8 A3 FF FF FF        call mov_ebp_40DB56	; set ebp the necessary base
0040DBAE 8D 75 F7              lea esi, [ebp-9]
0040DBAE                   ; -----------------------------------------------------------
0040DBB1 90 90 90 90 90 90+    db 30h dup(90h)
0040DBE1                   ; -----------------------------------------------------------
0040DBE1 8D 95 6C 03 00 00     lea edx, [ebp+36Ch]	; get address of Decryptor
0040DBE1                   ; -----------------------------------------------------------
0040DBE7 90 90 90 90 90 90+    db 21h dup(90h)
0040DC08                   ; -----------------------------------------------------------
0040DC08 B9 03 00 00 00        mov ecx, 3      ; size of table, only 3 entries
0040DC08                   ; -----------------------------------------------------------
0040DC0D 90 90 90 90 90 90+loop_inserting_all_engine_loc db 24h dup(90h) ; 
0040DC31                   ; -----------------------------------------------------------
0040DC31 8B 06                 mov eax, [esi]	; read one dword from table
0040DC31                   ; -------------------; each dword is address where Call are
0040DC33 90 90 90 90 90 90+    db 25h dup(90h)  ; to be inserted 
0040DC58                   ; -----------------------------------------------------------
0040DC58 83 EE 04              sub esi, 4	; mov pointer on table backwards
0040DC58                   ; -----------------------------------------------------------
0040DC5B 90 90 90 90 90 90+    db 2Dh dup(90h)
0040DC88                   ; -----------------------------------------------------------
0040DC88 C6 00 E8              mov byte ptr [eax], 0E8h ; set up the CALL byte
0040DC88                   ; -----------------------------------------------------------
0040DC8B 90 90 90 90 90 90+    db 2Dh dup(90h)
0040DCB8                   ; -----------------------------------------------------------
0040DCB8 40                    inc eax
0040DCB9 8B FA                 mov edi, edx		; edi -> address of Decryptor
0040DCBB 2B F8                 sub edi, eax		; calculate the Call distance
0040DCBB                   ; -----------------------------------------------------------
0040DCBD 90 90 90 90 90 90+    db 2Bh dup(90h)
0040DCE8                   ; -----------------------------------------------------------
0040DCE8 83 EF 04              sub edi, 4		; minus instruction length
0040DCEB 89 38                 mov [eax], edi  		; insert CAll distance after E8
0040DCEB                   ; -----------------------------------------------------------
						PATCHES made to force decrypt code
0040DCED 90 90 90 90 90 90+    db 28h dup(90h) ; push dword ptr [eax+4]
0040DCED 90 90 90 90 90 90+                    ; pushad
0040DCED 90 90 90 90 90 90+                    ; dec eax
0040DCED 90 90 90 90 90 90+                    ; call eax=>decrypt routine which is patched
0040DCED 90 90 90 90 90 90+                    ; popad
0040DCED 90 90 90 90 90 90+                    ; pop dword ptr [eax+4]
0040DCED 90 90 90 90                           ; mov word [eax-1], 7EB
0040DD15                   ; -----------------------------------------------------------
0040DD15 83 C0 08              add eax, 8		; 
0040DD18 03 40 FC              add eax, [eax-4]		; add code block length
0040DD1B 83 C0 10              add eax, 10h		; eax -> where Call Re-encryptor 
							  should be placed
0040DD1B                   ; -----------------------------------------------------------
0040DD1E 90 90 90 90 90 90+    db 2Eh dup(90h)		; Here another call is set up
0040DD4C                   ; -----------------------------------------------------------
0040DD4C C6 00 E8              mov byte ptr [eax], 0E8h ; 'F' ; mov byte ptr [eax], E9
0040DD4C                   ; -----------------------------------------------------------
0040DD4F 90 90 90 90 90 90+    db 2Fh dup(90h)		; to call Re-encryptor
0040DD7E                   ; -----------------------------------------------------------
0040DD7E 40                    inc eax
0040DD7E                   ; -----------------------------------------------------------
0040DD7F 90 90 90 90 90 90+    db 2Ch dup(90h)		; edi -> address of Re-encryptor
0040DDAB                   ; -----------------------------------------------------------
0040DDAB 8D BD 4E 07 00 00     lea edi, [ebp+74Eh] ; lea edi, [eax+8]
0040DDAB                   ; -----------------------------------------------------------
0040DDB1 90 90 90 90 90 90+    db 28h dup(90h)
0040DDD9                   ; -----------------------------------------------------------
0040DDD9 2B F8                 sub edi, eax		; calculate Call distance
0040DDD9                   ; -----------------------------------------------------------
0040DDDB 90 90 90 90 90 90+    db 23h dup(90h)
0040DDFE                   ; -----------------------------------------------------------
0040DDFE 83 EF 04              sub edi, 4		; minus instruction length
0040DDFE                   ; -----------------------------------------------------------
0040DE01 90 90 90 90 90 90+    db 25h dup(90h)
0040DE26                   ; -----------------------------------------------------------
0040DE26 89 38                 mov [eax], edi		; insert call distance
0040DE26                   ; -----------------------------------------------------------
0040DE28 90 90 90 90 90 90+    db 2Eh dup(90h)		; to finalise the Call
0040DE56                   ; -----------------------------------------------------------
0040DE56 49                    dec ecx			; decrease counter along the table
0040DE56                   ; -----------------------------------------------------------
0040DE57 90 90 90 90 90 90+    db 36h dup(90h)		; if not end of table, loop back
0040DE8D                   ; -----------------------------------------------------------
0040DE8D 0F 85 7A FD FF FF     jnz near ptr loop_inserting_all_engine_loc
0040DE8D                   ; -----------------------------------------------------------
0040DE93 90 90 90 90 90 90+    db 23h dup(90h)
0040DEB6                   ; -----------------------------------------------------------
0040DEB6 8D 7D 52              lea edi, [ebp+52h]
0040DEB9 B9 16 03 00 00        mov ecx, 316h		; overwrite code area above
0040DEBE F3 AA                 repe stosb		; with garbage
0040DEC0 61                    popa
0040DEC1 C3                    retn

The comments are pretty clear. Address of encrypted code are stored in tables, where each entry is read, then E8xxxxxxxx is placed there to Call Decryptor and location of Call to Re-encryptor is calculated based on the encrypted code block length. We need to patch these routine so that they will decrypt all code blocks listed on the table of addresses and not place Call to Decryptor/Encryptor into these address. I have made the necessary patches in orange as commented above. "Call eax" is used to simulate execution of the original exe and hence will trigger Call Decryptor which will then decrypt the code block following that address. However, we need to patch the Decryptor routine not to return execution to original exe like normal but to return execution to our patched loop. This can be done simply by correcting the stack.

We need to find the end of the Decryptor routine which looks like this
ret             ; return execution to just decrypted code block
and patch it to correct stack like this
pop eax     ; eax = address of decrypted code block
ret             ; return from our "Call eax" to our patched loop

4. The CRC trap

As mentioned above, there are 3 different set of Decryptor/Encryptor and hence each has a separate routine inserting those Calls into the target exe with a separate table containing address where those calls should be inserted to. In order to obtain a good dump, we need to patch all 3 routines to force decrypt the whole code section of the target exe. However, problem arises when one of these Calls Insertion routine has an encrypted table of address which uses the routine CRC to decrypt these table entries. Attempts to patch it will cause table entries to be decrypted incorrectly. Here is how the routine look like :

00408368                   Insert_Crypt_Engine_1: ; 
00408368 B9 4A 00 00 00        mov ecx, 4Ah	 ;  size of table 1
00408368                   ; -----------------------------------------------------------
0040836D 90 90 90 90 90 90+    db 27h dup(90h)
00408394                   ; -----------------------------------------------------------
00408394 8D 75 E8              lea esi, [ebp-18h] ; start of Table 1
00408394                   ; -----------------------------------------------------------
00408397 90 90 90 90 90 90+    db 27h dup(90h)
004083BE                   ; -----------------------------------------------------------
004083BE 8D 95 68 0B 00 00     lea edx, [ebp+0B68h]	; edx -> Decryptor address
004083BE                   ; -----------------------------------------------------------
004083C4 90 90 90 90 90 90+get_crypted_offset db 55h dup(90h) ; CODE XREF: ...
00408419                   ; -----------------------------------------------------------
00408419 8B 06                 mov eax, [esi]		; read encrypted entry from table
0040841B EB 31                 jmp short near ptr prepare_CRC_data_pointer
0040841B                   ; -----------------------------------------------------------
0040841D 90 90 90 90 90 90+set_no_of_loop db 27h dup(90h) ; CODE XREF: ...
00408444                   ; -----------------------------------------------------------
00408444 BB 1F 01 00 00        mov ebx, 11Fh
00408449 E9 25 01 00 00        jmp near ptr calculate_insertion_address
00408449                   ; -----------------------------------------------------------
0040844E 90 90 90 90 90 90+prepare_CRC_data_pointer db 2Eh dup(90h) ; CODE XREF: ...
0040847C                   ; -----------------------------------------------------------
0040847C 83 EE 04              sub esi, 4
0040847F EB 9C                 jmp short near ptr set_no_of_loop
0040847F                   ; -----------------------------------------------------------
00408481 90 90 90 90 90 90+get_call_distance db 2Fh dup(90h) ; CODE XREF: ...
004084B0                   ; -----------------------------------------------------------
004084B0 2B F8                 sub edi, eax
004084B2 E9 84 00 00 00        jmp near ptr minus_length_4_bytes
004084B2                   ; -----------------------------------------------------------
004084B7 90 90 90 90 90 90+set_up_CALL_decrypt db 2Eh dup(90h) ; CODE XREF: ...
004084E5                   ; -----------------------------------------------------------
004084E5 C6 00 E8              mov byte ptr [eax], 0E8h ; 'F' ; set up CALL
004084E8 E9 09 01 00 00        jmp near ptr minus_1_byte
004084E8                   ; -----------------------------------------------------------
004084ED 90 90 90 90 90 90+decrease_counter db 1Bh dup(90h) ; CODE XREF: ...
00408508                   ; -----------------------------------------------------------
00408508 49                    dec ecx		; decrease counter to check end of table
00408509 E9 C1 02 00 00        jmp check_end_of_table?
00408509                   ; -----------------------------------------------------------
0040850E 90 90 90 90 90 90+get_CALL_to_address db 26h dup(90h) ; CODE XREF: ...
00408534                   ; -----------------------------------------------------------
00408534 8B FA                 mov edi, edx
00408536 E9 46 FF FF FF        jmp near ptr get_call_distance
00408536                   ; -----------------------------------------------------------
0040853B 90 90 90 90 90 90+minus_length_4_bytes db 30h dup(90h) ; CODE XREF: ...
0040856B                   ; -----------------------------------------------------------
0040856B 83 EF 04              sub edi, 4	; decrease pointer to table entries by 4
0040856E E9 F3 01 00 00        jmp near ptr store_jump_distance
0040856E                   ; -----------------------------------------------------------
00408573 90 90 90 90 90 90+calculate_insertion_address db 27h dup(90h) ; CODE XREF: ...
0040859A                   ; -----------------------------------------------------------
0040859A                   CRC_loop:           ; CODE XREF: ...
0040859A 03 84 9D F6 05 00+    add eax, [ebp+ebx*4+5F6h]
004085A1 D1 C8                 ror eax, 1      ; loop calculating the address
004085A3 4B                    dec ebx         ; where CALL to decrypt code
004085A4 75 F4                 jnz short CRC_loop ; can be inserted to
004085A6 E9 0C FF FF FF        jmp near ptr set_up_CALL_decrypt ; <===   HERE do bpm 
004085A6                   ; -----------------------------------------------------------
004085AB 90 90 90 90 90 90+get_distance_call db 44h dup(90h) ; CODE XREF: ...
004085EF                   ; -----------------------------------------------------------
004085EF 2B F8                 sub edi, eax
004085F1 E9 E1 01 00 00        jmp near ptr byte_4087D7
004085F1                   ; -----------------------------------------------------------
004085F6 90 90 90 90 90 90+minus_1_byte db 29h dup(90h) ; CODE XREF: ...
0040861F                   ; -----------------------------------------------------------
0040861F 40                    inc eax
00408620 E9 E9 FE FF FF        jmp near ptr get_CALL_to_address
00408620                   ; -----------------------------------------------------------
00408625 90 90 90 90 90 90+byte_408625 db 24h dup(90h) ; CODE XREF: ...
00408649                   ; -----------------------------------------------------------
00408649 89 38                 mov [eax], edi
0040864B E9 9D FE FF FF        jmp near ptr decrease_counter
0040864B                   ; -----------------------------------------------------------
00408650 90 90 90 90 90 90+calculate_block_length db 1Eh dup(90h) ; CODE XREF: ...
0040866E                   ; -----------------------------------------------------------
0040866E 83 C0 08              add eax, 8      ; points to encrypted code
00408671 EB 00                 jmp short $+2
00408673 8B 58 FC              mov ebx, [eax-4] ; get crypted_length
00408673                   ; -----------------------------------------------------------
00408676 90 90 90 90 90 90+    db 2Eh dup(90h)
004086A4                   ; -----------------------------------------------------------
004086A4 C1 CB 03              ror ebx, 3	; ror ebx, 3
004086A4                   ; -----------------------------------------------------------
004086A7 90 90 90 90 90 90+    db 28h dup(90h)
004086CF                   ; -----------------------------------------------------------
004086CF 03 C3                 add eax, ebx	; eax -> where to insert Call Encryptor
004086D1 E9 C1 00 00 00        jmp near ptr get_end_pointer
004086D1                   ; -----------------------------------------------------------
004086D6 90 90 90 90 90 90+get_reencrypt_call_address db 26h dup(90h) ; CODE XREF: ...
004086FC                   ; -----------------------------------------------------------
004086FC 8D BD 9C 13 00 00     lea edi, [ebp+139Ch]
00408702 E9 A4 FE FF FF        jmp near ptr get_distance_call
00408702                   ; -----------------------------------------------------------
00408707 90 90 90 90 90 90+insert_encrypt_call db 2Dh dup(90h) ; CODE XREF: ...
00408734                   ; -----------------------------------------------------------
00408734 C6 00 E8              mov byte ptr [eax], 0E8h ; 'F'
00408737 EB 00                 jmp short $+2
00408737                   ; -----------------------------------------------------------
00408739 90 90 90 90 90 90+    db 27h dup(90h)
00408760                   ; -----------------------------------------------------------
00408760 40                    inc eax
00408761 E9 70 FF FF FF        jmp near ptr get_reencrypt_call_address
00408761                   ; -----------------------------------------------------------
00408766 90 90 90 90 90 90+store_jump_distance db 2Ah dup(90h) ; CODE XREF: ...
00408790                   ; -----------------------------------------------------------
00408790 89 38                 mov [eax], edi
00408792 E9 B9 FE FF FF        jmp near ptr calculate_block_length
00408792                   ; -----------------------------------------------------------
00408797 90 90 90 90 90 90+get_end_pointer db 30h dup(90h) ; CODE XREF: ...
004087C7                   ; -----------------------------------------------------------
004087C7 83 C0 10              add eax, 10h
004087CA E9 38 FF FF FF        jmp near ptr insert_encrypt_call
004087CF                   ; -----------------------------------------------------------
004087CF                   check_end_of_table?: ; CODE XREF: ...
004087CF 0F 85 1C FC FF FF     jnz near ptr get_crypted_offset+2Dh ; loop if not finished
004087D5 EB 3C                 jmp short _done_ ; 
004087D5                   ; -----------------------------------------------------------
004087D7 90 90 90 90 90 90+byte_4087D7 db 34h dup(90h) ; CODE XREF: ...
0040880B                   ; -----------------------------------------------------------
0040880B 83 EF 04              sub edi, 4
0040880E E9 12 FE FF FF        jmp near ptr byte_408625
00408813                   ; -----------------------------------------------------------
00408813                   _done_:             ; bpm here to wait till it finishes

I know this is hard to follow because the code flow is not linear but hey it is not my fault, it was the intention of PELock author to make our lives harder, I hope the coloring helps a bit. The algorithm is similar to the insertion algo in the previous code snippet, only differences are code block length calculation. and tables entries are decrypted on the fly. Basically the decryption of table entries to addresses of encrypted code block is done by the CRC_loop at 40859A. If you place a breakpoint at 4085A6 you will see that the decrypted address is stored in eax. Here I can think of two solutions to overcome CRC check, either we copy the whole original block of code into another memory location and patch the CRC routine to perform CRC check on that new location or we can again use softice breakpoints features. I opted for the latter, though your softice screen will flicker a lot, it is simpler to perform.

Basically, I let PELock loader goes on executing as normal, decrypt each table entry then insert the appropriate calls in front and after the encrypted code blocks. However, I will place a breakpoint at 4085A6 and store the decrypted addresses into the table so we will at least have a decrypted table in the end.
bpm 408813 x        ; the ending breakpoint for us when the whole insertion loop is finished!
bpm 4085A6 x do "dd esi+4 eax; x"    ; store decrypted value eax back into table
                                                ; esi pointer is already decremented by 4 so we have plus 4

Once softice screen stop flickering and our final breakpoint is reached, you will see in data window of softice our newly decrypted table of address of encrypted code block. All we have left to do is then insert some code by softice window to force decrypt all the codes block listed in this last table.
a eip

	mov ecx, 4A		; size of table
load_	lodsd			; read 1 entry from table
	push dword ptr [eax+5]	; preserve code block property values
	pushad
	call eax		; simulate original exe execution
	popad
	pop dword ptr [eax+5]	; restore code block property values
	mov word ptr [eax], 7EB	; patch Call Decryptor with a Jump Over
	mov ebx, word ptr [eax+5] ; read code block property
	ror ebx, 3		; get code block length
	add eax, ebx
	add eax, 19		; calculate address of Call Encryptor
	mov word ptr [eax], 7EB	; replace Call Encryptor with a Jump Over
	dec ecx			; decrease size of table
	test ecx, ecx
	jne load_		; get next entry of table if not done
	jmp eip			; infinite loop, DUMP here

Now, this procedure described above should be done last, after you have patched the other two insertion routine do force decrypt those code blocks listed in those two corresponding tables. This is so that when we let PELock run again and reach our infinite jump, ALL the encrypted codes block inside target EXE are fully decrypted and Calls to Decryptors and Re-encryptors and patched with simple Jumps Over.

5. Finding OEP

Now that we have obtained a good dump with all codes decrypted and fixed, it is time to run Imprec again and fix the Import Directory of our dump. The final task is of course finding OEP which is relatively simple for PElock. Looking at the disassembly and hex display of our dump.exe, we see that the first section is started with a lot of strings then followed by code so we can simply guess that OEP is right at the end of those strings.

Another and more generic of finding OEP in PELock protected apps would be finding where PELock jumps to OEP. This occurs soon after the three Insertion Routine above are performed to insert those Calls to Decryptors and Encryptors into the target exe.

00407578 E8 2B 66 00 00        call insert_crypt_engine_3 ; our insertion routine call
00407578                   ; -----------------------------------------------------------
0040757D 90 90 90 90 90 90+    db 28h dup(90h)		; that is responsible for inserting
004075A5                   ; -----------------------------------------------------------
004075A5 E8 0C 73 00 00        call insert_crypt_engine_2 ; those Call Decryptor/Encryptor
004075A5                   ; -----------------------------------------------------------
004075AA 90 90 90 90 90 90+    db 28h dup(90h)	   ; into the target exe main code section
004075D2                   ; -----------------------------------------------------------
004075D2 E8 C5 07 00 00        call unknown_import_stuff_and_insert_crypt_engine_1
004075D2                   ; -----------------------------------------------------------
004075D7 90 90 90 90 90 90+    db 28h dup(90h)	; these code would still be encrypted
<-------------SNIP-------------->		; and decrypted last right before reaching 
00407AA1 5D                    pop ebp		; OEP.
00407AA1                   ; -----------------------------------------------------------
00407AA2 90 90 90 90 90 90+    db 124h dup(90h)
00407BC6                   ; -----------------------------------------------------------
00407BC6 68 1E 1D 40 00        push 401D1Eh    ; OEP!!!
00407BC6                   ; -----------------------------------------------------------
00407BCB 90 90 90 90 90 90+    db 23h dup(90h)
00407BEE                   ; -----------------------------------------------------------
00407BEE C3                    retn

That is it, phew. Hence once you find any of the Insertion Routine, you can then check on stack to find where they are called from and place the appropriate bpm. Of course you should dump the loader there and look inside IDA for better view. From then onwards OEP, there would be simple loop decryption and SEH to prevent manual tracing but with IDA I am sure you can find the last bit of pushing OEP as above :).

6. Final thought

WOW, a hell of a protector. There are actually more things I wish to discuss about but this tutorial has become too long and I am sure if you reach here, you would not wanna read any more of my words. I am also tired of writing so maybe another time.

Overall, PELock is a pretty good protection and it was really fun reversing it. If PELock has inserted those Calls to Decryptor and Encryptor at packing time and removed the tables containing addresses of encrypted code block, it would be much harder to decrypt ALL code blocks as we cant be so sure if we have decrypt all blocks. There would be a problem because the loader will always be loaded at different offset so PElock would know the Call distance at packing time but she =) can always uses CALL dword ptr [xxxxxxxx] instead where xxxxxxx is some fixed address. Ah well, just an idea.

I hope you enjoy the tutorial. Again, comments are welcome and if you feel that I should improve or correct certain things about this tutorial, feel free me drop me a message on Fravia Message Board.

                                                        --------------------------------------------------------

This tutorial would not have been possible without the help of all my friends and previous tutorials writers. so a BIG thank you to ALL!

Last Edited : 30 Jan 2003

Cheers, 

   crUsAdEr